package com.enation.app.javashop.core.payment.service.impl;

import com.enation.app.javashop.core.payment.PaymentErrorCode;
import com.enation.app.javashop.core.payment.model.vo.CombinationPayBill;
import com.enation.app.javashop.core.payment.model.dos.PaymentMethodDO;
import com.enation.app.javashop.core.payment.model.dto.PayParam;
import com.enation.app.javashop.core.payment.model.enums.ClientType;
import com.enation.app.javashop.core.payment.model.enums.PaymentPluginEnum;
import com.enation.app.javashop.core.payment.model.enums.TradeType;
import com.enation.app.javashop.core.payment.model.vo.Form;
import com.enation.app.javashop.core.payment.model.vo.PayBill;
import com.enation.app.javashop.core.payment.service.*;
import com.enation.app.javashop.core.trade.TradeErrorCode;
import com.enation.app.javashop.core.trade.order.model.dos.OrderDO;
import com.enation.app.javashop.core.trade.order.model.dos.TradeDO;
import com.enation.app.javashop.core.trade.order.model.enums.OrderStatusEnum;
import com.enation.app.javashop.core.trade.order.model.vo.OrderDetailVO;
import com.enation.app.javashop.core.trade.order.service.OrderQueryManager;
import com.enation.app.javashop.core.trade.order.service.TradeQueryManager;
import com.enation.app.javashop.framework.database.DaoSupport;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.logs.Debugger;
import com.enation.app.javashop.framework.util.CurrencyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * @author fk
 * @version v2.0
 * @Description: 订单支付
 * @date 2018/4/1617:10
 * @since v7.0.0
 */
@Service
public class OrderPayManagerImpl implements OrderPayManager {

    @Autowired
    @Qualifier("tradeDaoSupport")
    private DaoSupport daoSupport;

    @Autowired
    private OrderQueryManager orderQueryManager;

    @Autowired
    private TradeQueryManager tradeQueryManager;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private PaymentMethodManager paymentMethodManager;

    @Autowired
    private Debugger debugger;

    @Autowired
    private List<PaymentPluginManager> paymentPluginList;

    @Override
    public Form pay(PayParam param) {
        //1.对订单状态进行检测，只有已确认的订单才可以进行支付
        boolean isLegitimate;
        String tradeType = param.getTradeType();
        String sn = param.getSn();
        if (tradeType.equals(TradeType.order.name())) {
            isLegitimate = this.checkStatus(sn, TradeType.order, 0);
        } else {
            isLegitimate = this.checkStatus(sn, TradeType.trade, 0);
        }

        //2.判断订单状态是否已确认可以订单支付，否抛出异常
        if (!isLegitimate) {
            throw new ServiceException(PaymentErrorCode.E506.code(), "该交易状态不正确，无法支付");
        }

        //3.查询订单或交易单的钱包支付金额和总金额
        String sql, updateSql;
        double totalPrice, walletPayPrice;
        if (TradeType.trade.name().equals(param.getTradeType())) {
            sql = "select * from es_trade where trade_sn = ?";
            updateSql = "update es_order set payment_plugin_id = ?,payment_method_name = ?,trade_type= ? where trade_sn = ?";
            TradeDO trade = this.daoSupport.queryForObject(sql, TradeDO.class, sn);
            walletPayPrice = trade.getWalletPayPrice();
            totalPrice = trade.getTotalPrice();
        } else {
            sql = "select * from es_order where sn = ?";
            updateSql = "update es_order set payment_plugin_id = ?,payment_method_name = ?,trade_type= ? where sn = ?";
            OrderDO order = this.daoSupport.queryForObject(sql,OrderDO.class, sn);
            walletPayPrice = order.getWalletPayPrice();
            totalPrice = order.getOrderPrice();
        }

        //4.判断支付方式，修改订单支付方式
        PayBill bill;
        if (walletPayPrice > 0) {
            //这里将交易类型设置成order，因为创建订单时如果有钱包支付，会生成order类型的交易单，用out_trade_sn冻结金额，必须复用交易单
            //不论是order还是trade都视为使用order类型,同时将tradeType也存入order表中，退款时需要
            if (TradeType.trade.name().equals(param.getTradeType())) {
                param.setSn(this.daoSupport.queryForString("select sn from es_order where trade_sn = ? LIMIT 0,1", sn));
            } else {
                param.setSn(sn);
            }
            tradeType = TradeType.order.toString();
            param.setTradeType(tradeType);
            int result = BigDecimal.valueOf(totalPrice).compareTo(BigDecimal.valueOf(walletPayPrice));

            //钱包支付金额大于0，总支付金额等于钱包支付金额为---》全部钱包支付
            if (result == 0) {
                param.setPaymentPluginId(PaymentPluginEnum.walletPayPlugin.toString());
                bill = this.initBill(param, walletPayPrice);
            } else if (result > 0) {
                //钱包支付金额大于0，总支付金额大于钱包支付金额为---》部分钱包支付，部分微信支付
                //组装微信支付单，这里设置PaymentPluginId是为了组装两条支付单
                List<PayBill> payBillList = new ArrayList<>();
                param.setPaymentPluginId(PaymentPluginEnum.weixinPayPlugin.toString());
                PayBill weixinPayBill = this.initBill(param, CurrencyUtil.sub(totalPrice, walletPayPrice));
                payBillList.add(weixinPayBill);

                //组装钱包支付单，这里设置PaymentPluginId是为了组装两条支付单
                param.setPaymentPluginId(PaymentPluginEnum.walletPayPlugin.toString());
                bill = this.initBill(param, walletPayPrice);
                payBillList.add(bill);
                bill = new CombinationPayBill(payBillList);

                //最终将PaymentPluginId设置为组合支付,后续要获取支付插件
                param.setPaymentPluginId(PaymentPluginEnum.mergePayPlugin.toString());
            } else {
                //钱包支付金额大于0，总支付金额小于钱包支付金额---》支付异常：钱包支付金额不得超过总支付金额
                throw new RuntimeException("支付异常：钱包支付金额不得超过总金额");
            }

        } else if (walletPayPrice == 0){
            //钱包支付金额等于0，为全部微信支付
            param.setPaymentPluginId(PaymentPluginEnum.weixinPayPlugin.toString());
            bill = this.initBill(param, totalPrice);
        } else {
            //钱包支付金额小于0，支付异常
            throw new RuntimeException("支付异常：钱包支付金额不得为负数");
        }

        debugger.log("准备对以下bill进行支付");
        debugger.log(bill.toString());

        //5.修改订单的支付方式,生成对应方式的支付单
        String paymentPluginId = param.getPaymentPluginId();
        PaymentMethodDO paymentMethod = this.getPaymentPlugin(paymentPluginId);
        PaymentPluginManager paymentPlugin = this.findPlugin(paymentPluginId);
        if(ObjectUtils.isEmpty(paymentPlugin)){
            throw new RuntimeException("支付异常：支付插件获取失败");
        }
        //修改订单的支付方式
        this.daoSupport.execute(updateSql, paymentPlugin.getPluginId(), paymentPlugin.getPluginName(), tradeType, sn);
        //生成对应方式的支付单
        bill = paymentPlugin.createPaymentBill(bill, paymentMethod);
        debugger.log("开始调起支付插件：" + paymentPlugin.getPluginId());

        //6.确定最终支付插件,发起支付请求
        String pluginId = bill.getPluginId();
        this.getPaymentPlugin(pluginId);
        PaymentPluginManager finalPaymentPlugin = this.findPlugin(pluginId);
        if(ObjectUtils.isEmpty(finalPaymentPlugin)){
            throw new RuntimeException("支付异常：支付插件获取失败");
        }
        return finalPaymentPlugin.pay(bill);
    }

    private PaymentMethodDO getPaymentPlugin(String paymentPluginId){
        PaymentMethodDO paymentMethod = this.paymentMethodManager.getByPluginId(paymentPluginId);
        if (paymentMethod == null) {
            debugger.log("未找到相应的支付方式[" + paymentPluginId + "]");
            throw new ServiceException(PaymentErrorCode.E501.code(), "未找到相应的支付方式[" + paymentPluginId + "]");
        }
        return paymentMethod;
    }

    private PayBill initBill(PayParam param, Double totalPayPrice){
        PayBill bill = new PayBill();
        bill.setPluginId(param.getPaymentPluginId());
        bill.setOrderPrice(totalPayPrice);
        bill.setPayMode(param.getPayMode());
        bill.setTradeType(TradeType.valueOf(param.getTradeType()));
        bill.setClientType(ClientType.valueOf(param.getClientType()));
        bill.setSn(param.getSn());
        return bill;
    }

    /**
     * 检测交易（订单）是否可以被支付
     * @param sn        订单（交易）号
     * @param tradeType 交易类型
     * @param times     次数
     * @return 是否可以被支付
     */
    private boolean checkStatus(String sn, TradeType tradeType, Integer times) {
        try {
            //如果超过三次则直接返回false，不能支付
            if (times >= 3) {
                return false;
            }
            //订单或者交易状态
            String status;
            if (tradeType.equals(TradeType.order)) {
                //获取订单详情，判断订单是否是已确认状态
                OrderDetailVO orderDetailVO = orderQueryManager.getModel(sn, null);
                if (orderDetailVO != null) {
                    status = orderDetailVO.getOrderStatus();
                } else {
                    throw new ServiceException(TradeErrorCode.E459.code(), "此订单不存在");
                }
            } else {
                //获取交易详情，判断交易是否是已确认状态
                TradeDO tradeDO = tradeQueryManager.getModel(sn);
                if (tradeDO != null) {
                    status = tradeDO.getTradeStatus();
                } else {
                    throw new ServiceException(TradeErrorCode.E458.code(), "此交易不存在");
                }
            }
            //检验交易或者订单状态是否是已确认可被支付
            if (!status.equals(OrderStatusEnum.CONFIRM.value())) {
                Thread.sleep(1000);
                return this.checkStatus(sn, tradeType, ++times);
            } else {
                return true;
            }
        } catch (Exception e) {
            logger.error("检测订单是否可被支付,订单不可被支付，重试检测" + times + ",次，消息" + e.getMessage());
            this.checkStatus(sn, tradeType, ++times);
        }
        return false;
    }

    /**
     * 查找支付插件
     * @param pluginId 支付插件ID
     */
    private PaymentPluginManager findPlugin(String pluginId) {
        for (PaymentPluginManager plugin : paymentPluginList) {
            if (plugin.getPluginId().equals(pluginId)) {
                return plugin;
            }
        }
        return null;
    }

}
